package com.ffmpeg.util.app.impl;

import java.io.*;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;

import com.ffmpeg.util.app.FfmpegService;
import com.ffmpeg.util.app.FileInfoService;
import com.ffmpeg.util.domain.entity.FileInfo;
import com.ffmpeg.util.infra.config.FileConfigProperties;
import com.ffmpeg.util.infra.constant.FileConstant;
import com.ffmpeg.util.infra.handle.FfmpegHandle;
import com.ffmpeg.util.infra.handle.file.DownloadHandle;
import com.ffmpeg.util.infra.util.DateUtils;
import com.ffmpeg.util.infra.util.FfmpegUtils;
import com.ffmpeg.util.infra.util.FileUtil;
import com.ffmpeg.util.infra.util.ProcessBuilderUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.web.multipart.MultipartFile;

/**
 * @author :wm
 * @description :改变文件实现类
 */
@Service
public class FfmpegServiceImpl implements FfmpegService {
    private final static Logger logger = LoggerFactory.getLogger(FfmpegServiceImpl.class);

    private DownloadHandle downloadHandle;

    private FileUtil fileUtil;

    private FileInfoService fileInfoService;

    @Autowired
    public void setDownloadHandle(DownloadHandle downloadHandle) {
        this.downloadHandle = downloadHandle;
    }

    @Autowired
    public void setFileUtil(FileUtil fileUtil) {
        this.fileUtil = fileUtil;
    }

    @Autowired
    public void setFileInfoService(FileInfoService fileInfoService) {
        this.fileInfoService = fileInfoService;
    }

    @Resource
    private FileConfigProperties fileConfigProperties;
    @Resource
    private ProcessBuilderUtils processBuilderUtils;
    @Resource
    private FfmpegUtils ffmpegUtils;
    @Resource
    private FfmpegHandle ffmpegHandle;
    @Resource
    private TaskExecutor taskExecutor;

    @Override
    public void compressedVideoByPath(String path, String parentRoot) {
        //执行来源文件校验逻辑
        File file = fileUtil.checkSourceFile(path);

        //获取目标文件目录，若没有则创建
        String targetFilePath = fileUtil.getTargetFilePath(file, null, parentRoot);

        //目标文件中存在该导出文件的处理方式
        String outPutFilePath = ffmpegUtils.getDealWhenExistHandle().executeFileWhenExist(targetFilePath);

        //组装并执行命令
        String ffmpegPath = fileConfigProperties.getPrefixFfmpegPath();
        String command = ffmpegUtils.buildDefaultCompressCommand(ffmpegPath, path, outPutFilePath);
        this.dealFileByFfmpegCommand(command);
    }

    @Override
    public void compressedVideoByPathWithThread(String path) {
        //视频分片
        String targetDirPath = this.shardingVideo(path);
        //将path分解成dirPath, fileName, suffix
        FileInfo fileInfo = fileInfoService.queryFileInfoByAbsolutePath(path, false);
        //遍历分片后的文件并使用多线程压缩
        File file = new File(targetDirPath);
        File[] files = file.listFiles((File item) -> StringUtils.startsWith(item.getName(), fileInfo.getFileName())
                && !StringUtils.equalsAny(item.getName(), fileInfo.getFileName()));
        if (Objects.isNull(files)) {
            return;
        }
        CountDownLatch countDownLatch = new CountDownLatch(files.length);
        Date startDateTime  = new Date();
        logger.debug("start date {}", startDateTime);
        for (File fileTemp : files) {
            taskExecutor.execute(() -> {
                try {
                    this.compressedVideoByPath(fileTemp.getPath(), fileInfo.getFileName());
                } finally {
                    countDownLatch.countDown();
                }
            });
        }

        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            logger.error("compress file error{}", e.getMessage());
        }
        Date endDateTime = new Date();
        logger.info("file compress success, cost {} s", DateUtils.getCostValue(startDateTime, endDateTime));
        this.mergeVideo(fileConfigProperties.getExportDirPath() + fileInfo.getFileName(), fileInfo.getFileName(), fileInfo.getFileType());
    }

    @Override
    public void dealFileByFfmpegCommand(String command) {
        this.dealFileByFfmpegCommandAndArgs(command, null);
    }

    @Override
    public void dealFileByFfmpegCommandAndArgs(String command, Map<String, String> args) {
        //注意:此处只替换环境变量，不会对command中的变量进行替换，因此不要将导出导入文件路径等写成变量
        logger.info("command: {}", command);
        Assert.isTrue(StringUtils.isNotEmpty(command), "error.command.empty");
        ProcessBuilder processBuilder = processBuilderUtils.generatorProcessBuilder(command);
        if (Objects.nonNull(args)) {
            processBuilder.environment().putAll(args);
        }
        ffmpegHandle.executeProcess(processBuilder);
    }

    @Override
    public String compressedVideoByFile(MultipartFile file, Boolean deleteTargetFileFlag) {
        String outPutPath = fileConfigProperties.getExportDirPath() + file.getOriginalFilename();
        try {
            // 保存上传的文件
            String filePath = fileConfigProperties.getTmpDirPath() + file.getOriginalFilename();
            Assert.isTrue(fileUtil.findWithCreateDir(fileConfigProperties.getTmpDirPath()), "error.find_or_create.file");
            file.transferTo(new File(filePath));

            // 执行FFmpeg命令进行压缩
            String command = ffmpegUtils.buildDefaultCompressCommand(fileConfigProperties.getPrefixFfmpegPath(), filePath, outPutPath);
            this.dealFileByFfmpegCommand(command);

            // 删除上传的原始文件
            Assert.isTrue(new File(filePath).delete(), "error.tmp_file.delete");
            // 删除生成的文件
            if (deleteTargetFileFlag) {
                Assert.isTrue(new File(outPutPath).delete(), "error.output_file.delete");
            }
        } catch (IOException ioException) {
            logger.error(ioException.getMessage());
            return null;
        }
        return outPutPath;
    }

    @Override
    public void compressedVideoByFileAndDownload(MultipartFile file, HttpServletResponse response) {
        //下载压缩后的文件
        Assert.isTrue(downloadHandle.fileDownLoad(response,
                this.compressedVideoByFile(file, true)), "error.file.download");
    }

    @Override
    public String shardingVideo(String path) {
        File file = fileUtil.checkSourceFile(path);

        //目标文件夹还需要再新建文件夹，新建文件夹中放置分片的视频
        String fileDir = fileUtil.getFileNameWithoutSuffix(file.getName());
        String dirPath = fileConfigProperties.getTmpDirPath() + fileDir;
        fileUtil.findWithCreateDir(dirPath);
        String suffix = fileUtil.getFileSuffix(file.getName());
        String targetPath = dirPath + "\\" + fileDir + "_%d." + suffix;

        //拼接命令
        String command = String.format(FfmpegUtils.DEFAULT_SHARDING_COMMAND, fileConfigProperties.getPrefixFfmpegPath(), path, 10, targetPath);
        this.dealFileByFfmpegCommand(command);
        return dirPath;
    }

    @Override
    public void mergeVideo(String dirPath, String fileName, String suffix) {
        Assert.isTrue(ffmpegUtils.generatorMergeTxtByDir(dirPath, fileName, suffix), "error.generator_merge_txt.failed");
        String command = String.format(FfmpegUtils.DEFAULT_MERGE_COMMAND, fileConfigProperties.getPrefixFfmpegPath(), dirPath + fileConfigProperties.getMergeTxtPath(),
                dirPath + "\\" + Optional.ofNullable(fileName).orElse(fileConfigProperties.getMergeVideoName())
                        + (StringUtils.startsWith(suffix, FileConstant.Symbol.POINT) ? suffix : FileConstant.Symbol.POINT + suffix));
        this.dealFileByFfmpegCommand(command);
    }
}
